package common

import (
	"animal/octopus/config"
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/glebarez/sqlite"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"

	log "github.com/sirupsen/logrus"
)

var database *gorm.DB

func DB(db *gorm.DB) *gorm.DB {
	if db == nil {
		return database
	}
	return db
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func openMysql(drop bool) error {
	dsn1 := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8mb4&parseTime=True&loc=Local",
		config.Instance().Data.Mysql.User,
		config.Instance().Data.Mysql.Pass,
		config.Instance().Data.Mysql.Host,
		config.Instance().Data.Mysql.Port)

	// connect database server
	var err error
	database, err = gorm.Open(mysql.Open(dsn1), &gorm.Config{})
	if err != nil {
		log.WithFields(log.Fields{
			"user": config.Instance().Data.Mysql.User,
			"host": config.Instance().Data.Mysql.Host,
			"port": config.Instance().Data.Mysql.Port,
			"name": config.Instance().Data.Mysql.Name,
		}).Error("open database failed")
		return err
	}

	// need drop database in unit test case
	if drop {
		database.Exec(fmt.Sprintf("DROP DATABASE %s",
			config.Instance().Data.Mysql.Name))
		log.WithField("name", config.Instance().Data.Mysql.Name).Info("drop database")
	}

	// create database
	sql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s",
		config.Instance().Data.Mysql.Name)
	if database.Exec(sql) == nil {
		log.WithFields(log.Fields{
			"user": config.Instance().Data.Mysql.User,
			"host": config.Instance().Data.Mysql.Host,
			"port": config.Instance().Data.Mysql.Port,
			"name": config.Instance().Data.Mysql.Name,
		}).Error("create database failed")
		return errors.New("create database failed")
	}

	log.WithFields(log.Fields{
		"user": config.Instance().Data.Mysql.User,
		"host": config.Instance().Data.Mysql.Host,
		"port": config.Instance().Data.Mysql.Port,
		"name": config.Instance().Data.Mysql.Name,
	}).Info("create database success")

	dsn2 := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		config.Instance().Data.Mysql.User,
		config.Instance().Data.Mysql.Pass,
		config.Instance().Data.Mysql.Host,
		config.Instance().Data.Mysql.Port,
		config.Instance().Data.Mysql.Name)

	var gormConfig gorm.Config
	if config.Instance().Data.Debug {
		gormConfig.Logger = logger.Default.LogMode(logger.Info)
	}

	// open database
	database, err = gorm.Open(mysql.Open(dsn2), &gormConfig)
	if err != nil {
		log.WithFields(log.Fields{
			"user": config.Instance().Data.Mysql.User,
			"host": config.Instance().Data.Mysql.Host,
			"port": config.Instance().Data.Mysql.Port,
			"name": config.Instance().Data.Mysql.Name,
		}).Error("open database failed")
		return err
	}

	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func openSqlite(drop bool) error {
	fullFilePath := filepath.Join(filepath.Dir(os.Args[0]), "dolphin.db")
	if drop {
		if _, err := os.Stat(fullFilePath); !os.IsNotExist(err) {
			if os.Remove(fullFilePath) != nil {
				log.Fatal(err)
			}
		}
	}

	var gormConfig gorm.Config
	if config.Instance().Data.Debug {
		gormConfig.Logger = logger.Default.LogMode(logger.Info)
	}

	var err error
	database, err = gorm.Open(sqlite.Open(fullFilePath), &gormConfig)
	if err != nil {
		log.Error("open sqlite failed")
		return err
	}

	return nil
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func OpenDatabase(drop bool) error {
	if config.Instance().Data.Mysql.Name != "" {
		return openMysql(drop)
	} else {
		return openSqlite(drop)
	}
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func FindItemByName[T any](db *gorm.DB, name string) (*T, bool) {
	var items []T
	result := DB(db).Where("name = ?", name).Find(&items)
	if result.Error != nil {
		log.WithFields(log.Fields{
			"name":  name,
			"error": result.Error,
		}).Error("database error")
		return nil, false
	}

	if len(items) > 0 {
		return &items[0], true
	} else {
		return nil, false
	}
}

// ------------------------------------------------------------------------------
//
// ------------------------------------------------------------------------------
func FindItemByID[T any](db *gorm.DB, id uint) (*T, bool) {
	var items []T
	result := DB(db).Where("id = ?", id).Find(&items)
	if result.Error != nil {
		log.WithFields(log.Fields{
			"ID":    id,
			"error": result.Error,
		}).Error("data base error")
		return nil, false
	}

	if len(items) > 0 {
		return &items[0], true
	} else {
		return nil, false
	}
}
